import sys 


def to_number (val):
  """convert string to float or int depending on format"""
  if val == None: return val

  if isinstance(val, (int, float)): return val

  val = val.strip()
  if val == 'U' or val == '': return None
  return int(val) if val.find('.') == -1 else float(val)
 
def to_string (val):
  return val.__str__()

def intend (val):
  return "    " + val.replace ("\n", "\n    ")

class MuninNodeStateDiff(object):
  def __init__(self, left, right):
    self.left  = left
    self.right = right
    
  def __str__ (self):
    if not self.is_different():
      return "nothing changed"
    out = ""
    if not self.left.accessible == self.right.accessible: 
      out += "whole node state changed %s -> %s\n" % ('accessible' if self.left.accessible else 'not accessible', 'accessible' if self.right.accessible else 'not accessible')
      if self.left.accessible:
        out +="    new message is: %s\n" % self.right.message 
      if self.right.accessible:
        out +="    old message was: %s\n" % self.left.message 
    if self.right.accessible:
      compare_data = self.compare()

      if (len(compare_data['changed']) > 0):
        for module, diff in compare_data['changed'].iteritems():
          out += intend(diff.__str__())+"\n"

      if (len(compare_data['disappeared']) > 0):
        out += "modules disappeared (last known state follows):\n"
        for module, status in compare_data['disappeared'].iteritems():
          out += intend(status.__str__())+"\n"

      if (len(compare_data['added']) > 0):
        out += "modules added:\n"
        for module, status in compare_data['added'].iteritems():
          out += intend(status.__str__())+"\n\n"
        
          
    return out

  def compare (self):
    disappeared_modules = self.left.states.keys()

    added = {}
    changed = {}
    disappeared = {}
    
    for module, module_state in self.right.states.iteritems():
      try:
        disappeared_modules.remove(module)
 #       print "removed %s from disappeared list" % module
      except ValueError:
        added[module] = module_state
#        print "added module %s" % module
        continue

      module_diff = self.left.states[module].diff(module_state)
      if module_diff.is_different():
        #print "%s is different" % module
        #print module_diff
        changed[module] = module_diff
    
    for module in disappeared_modules:
      disappeared[module] = self.left.states[module]
    
    return {'disappeared': disappeared, 'added': added, 'changed': changed}
    
    

  def is_different(self):
    substates = self.compare()
    return not (self.left.accessible == self.right.accessible \
                and len(substates['disappeared']) == 0 \
                and len(substates['changed']) == 0 \
                and len(substates['added']) == 0)
                


class MuninNodeState(object):

  def __init__(self, accessible = False, message = None):
    self.message = message
    self.accessible = accessible
    self.time = None
    self.states = {}
  
  def diff(self, state):
    return MuninNodeStateDiff(self, state)

  
class MuninModuleStateDiff(object):
  def __init__(self, left, right):
    self.left = left
    self.right = right
    
  def __str__ (self):
    if not self.is_different():
      return "nothing changed"

    out = self.right.data['title']+":\n"
    compare_data = self.compare()
    if (len(compare_data['changed']) > 0):
      for property, diff in compare_data['changed'].iteritems():
        out += "    %s: %s\n" % (property, diff.__str__())
    
    if (len(compare_data['disappeared']) > 0):
      out += "    properties disappeared (last known state):\n"
      for property, status in compare_data['disappeared'].iteritems():
        out += "        %s: %s\n" % (property, status.__str__())

    if (len(compare_data['added']) > 0):
      out += "    properties added:\n"
      for property, status in compare_data['added'].iteritems():
        out += "        %s: %s\n" % (property, status.__str__())

    return out
  
  def compare (self):
    disappeared_properties = self.left.states.keys()

    added = {}
    changed = {}
    disappeared = {}
    
    for property, property_state in self.right.states.iteritems():
      try:
        disappeared_properties.remove(property)
      except ValueError:
        added[property] = property_state
        continue

      property_diff = self.left.states[property].diff(property_state)
      if property_diff.is_different():
        changed[property] = property_diff
    
    for property in disappeared_properties:
      disappeared[property] = self.left.states[property]
    
    return {'disappeared': disappeared, 'added': added, 'changed': changed}
    
  
  def is_different (self):
    substates = self.compare()
    return not (len(substates['changed']) == 0\
           and len(substates['added']) == 0\
           and len(substates['disappeared']) == 0)
  

class MuninModuleState(object):
  supported_options = {'info': (None, 'graph_info'),\
                       'total': (None, 'graph_total'),\
                       'title': (None, 'graph_title'),\
                       'period': ('second', 'graph_period'),\
                       'vlabel': ('', 'graph_vlabel'),\
                       'category': ('Other', 'graph_category')}
  
  def __init__(self, options = {}):
    self.data = {}
    for option, value in self.supported_options.iteritems():
      try:
        self.data[option] = options[value[1]]
      except KeyError:
        self.data[option] = value[0] #use default

    self.states = {}
  
  def __str__(self):
    out = []
    for property, state in self.states.iteritems():
      out.append(state.__str__())
    return self.data['title'].__str__() + ":\n    " + "\n    ".join(out)
    
  
  def diff(self, state):
    return MuninModuleStateDiff(self, state)

    


class MuninPropertyStateDiff(object):

  def __init__(self, left, right):
    self.left = left
    self.right = right
    
  def __str__ (self):
    if self.is_different():
      return "status changed " + self.left.get_level() + " -> " + self.right.get_level() + " (" + self.right.data['value'].__str__()+")"
    return "nothing changed"
  
  def is_different(self):
    return not self.left.get_level() == self.right.get_level()


class MuninPropertyError(Exception):
  pass

class MuninPropertyRequiredFieldMissing(MuninPropertyError):
  def __init__(self, field):
    self.field = field
    MuninPropertyError.__init__(self, "required field '%s' is missing" % field)

class MuninPropertyState(object):
  """
  label must always be specified
  """
  required_fields = ['label']
  
  supported_fields = \
    {'label': to_string, \
     'info': to_string, \
     'extinfo': to_string, \
     'max': to_number, \
     'min': to_number, \
     'warning': to_number, \
     'critical': to_number, \
     'value': to_number, \
     }

  def __init__(self, data = {}):
    """
    """
    self.data = {}
    for field, wrapper in self.supported_fields.iteritems():
      try:
        value = wrapper(data[field])
      except KeyError: 
        if field in self.required_fields:
          raise MuninPropertyRequiredFieldMissing(field)
        value = None #the default one. probably will need some way to determine custom defaults
      
      self.data[field] = value
    

  def get_level(self):
    if self.data['value'] == None: 
      return 'Unknown'
    
    level = 'Ok'
    if not self.data['warning'] == None and self.data['value'] >= self.data['warning']:
      level = 'Warning'
    if not self.data['critical'] == None and self.data['value'] >= self.data['critical']:
      level = 'Critical'
    return level
  
  def __str__(self):
    return self.data['label'].__str__() + ": " + self.data['value'].__str__() + " which is " + self.get_level()
  
  def diff(self, state):
    return MuninPropertyStateDiff(self, state)




